// Memory
// (c) jimon game studio

#include "jeMemory.h"
#include "jeThreads.h"
#include "jeDebug.h"

#include <vector>
#include <map>
#include <fstream>
#include <stdlib.h>

#ifdef JE_MEMORY_MANAGEMENT

namespace je
{
	namespace core
	{
		//! Memory Manager Debug Record
		struct MemoryManagerDebugRecord
		{
			void * Pointer;
			const c8 * FileName;
			s32 LineNum;
			s32 Type;
		};

		//! Reference Count
		s32 CountReferences = 0;

		//! Memory Debug Map
		typedef std::map<void*,MemoryManagerDebugRecord*> MemDebugRecords;

		//! Memory Debug pair for map
		typedef std::pair<void*,MemoryManagerDebugRecord*> MemDebugRecordsPair;

		//! Mem Records vector Pointer
		MemDebugRecords * DebugRecords = NULL;

		//! Memory Reference Counter, need for init or deinit memory manager on grab\drop operations
		s32 MemoryReferenceCounter = 0;

		//! Alloc memory
		void * JE_ENGINE_FUNCTION_CALL Alloc(je_size_t Size)
		{
			void * Pointer = NULL;

			#pragma JE_THREAD_CRITICAL (___jeMemory_Memory)
			{
				Pointer = malloc(Size);
			}

			JEASSERT(Pointer);
			return Pointer;
		}

		//! Realloc memory
		void * JE_ENGINE_FUNCTION_CALL Realloc(void * Pointer,je_size_t Size)
		{
			JEASSERT(Pointer)

			void * NewPointer = NULL;

			#pragma JE_THREAD_CRITICAL (___jeMemory_Memory)
			{
				NewPointer = realloc(Pointer,Size);
			}

			JEASSERT(NewPointer);
			return NewPointer;
		}

		//! Free memory
		void JE_ENGINE_FUNCTION_CALL Free(void * Pointer)
		{
			JEASSERT(Pointer)

			#pragma JE_THREAD_CRITICAL (___jeMemory_Memory)
			{
				free(Pointer);
			}
		}

		//! Add Pointer To Manager
		void JE_ENGINE_FUNCTION_CALL AddPointer(void * Pointer,const c8 * FileName,s32 LineNum,s32 Type)
		{
			MemoryManagerDebugRecord * mrecord = new MemoryManagerDebugRecord;
			mrecord->Pointer = Pointer;
			mrecord->FileName = FileName;
			mrecord->LineNum = LineNum;
			mrecord->Type = Type;

			#pragma JE_THREAD_ATOMIC
			CountReferences++;

			#pragma JE_THREAD_CRITICAL (___jeMemory_DebugPointer)
			{
				DebugRecords->insert(MemDebugRecordsPair(Pointer,mrecord));
			}
		}

		//! Remove Pointer From Manager
		void JE_ENGINE_FUNCTION_CALL RemovePointer(void * Pointer,const c8 * FileName,s32 LineNum,s32 Type)
		{
			#pragma JE_THREAD_ATOMIC
			CountReferences--;

			#pragma JE_THREAD_CRITICAL (___jeMemory_DebugPointer)
			{
				if(DebugRecords->size())
				{
				MemDebugRecords::iterator Iter = DebugRecords->find(Pointer);
					if(Iter != DebugRecords->end())
					{
						delete Iter->second;
						DebugRecords->erase(Iter);
					}
				}
			}
		}

		//! Grab memory manager like junknown work
		void JE_ENGINE_FUNCTION_CALL MemoryGrab()
		{
			JEASSERT(MemoryReferenceCounter >= 0)

			if(!MemoryReferenceCounter)
			{
				CountReferences = 0;
				DebugRecords = new MemDebugRecords;
			}

			MemoryReferenceCounter++;
		}

		//! Drop memory manager like junknown work
		void JE_ENGINE_FUNCTION_CALL MemoryDrop()
		{
			JEASSERT(MemoryReferenceCounter)
			MemoryReferenceCounter--;

			if(!MemoryReferenceCounter)
			{
				#ifdef JE_LOG_TOFILE
				std::fstream fs(JE_LOG_FILE);
				fs.seekp(0,std::ios::end);
				fs << "----- Enesea Core Memory Log\n";

				if(CountReferences || DebugRecords->size())
					for(MemDebugRecords::iterator Iter = DebugRecords->begin();Iter != DebugRecords->end();Iter++)
					{
						MemoryManagerDebugRecord * mrecord = Iter->second;

						if(mrecord->FileName)
							fs << "Leak : " << mrecord->Pointer << " in " << mrecord->FileName << " : " << mrecord->LineNum << " (t" << mrecord->Type << ")\n";
						else
							fs << "Leak : " << mrecord->Pointer << " (no info)\n";
					}

				fs << "--- Reference Counter : " << CountReferences << "\n";
				fs << "--- Debug Records Count : " << DebugRecords->size() << "\n";

				fs.close();
				#endif

				#ifdef JE_LOG_TOCONSOLE
				if(CountReferences || DebugRecords->size())
				{
					printf("\n----- Enesea Core Memory Log\n");

					for(MemDebugRecords::iterator Iter = DebugRecords->begin();Iter != DebugRecords->end();Iter++)
					{
						MemoryManagerDebugRecord * mrecord = Iter->second;

						if(mrecord->FileName)
							printf("Leak : %p in %s : %i (t%i)\n",mrecord->Pointer,mrecord->FileName,mrecord->LineNum,mrecord->Type);
						else
							printf("Leak : %p (no info)\n",mrecord->Pointer);
					}

					printf("--- Reference Counter : %i\n",CountReferences);
					printf("--- Debug Records Count : %i\n\n",(s32)DebugRecords->size());
				}
				#endif

				if(CountReferences || DebugRecords->size())
				{
					for(MemDebugRecords::iterator Iter = DebugRecords->begin();Iter != DebugRecords->end();Iter++)
						delete Iter->second;
					DebugRecords->clear();
				}

				delete DebugRecords;
			}
		}
	}
}

#else

namespace je
{
	namespace core
	{
		//! Alloc memory
		void * Alloc(je_size_t Size){return malloc(Size);}

		//! Free memory
		void Free(void * Pointer){free(Pointer);}

		//! Add Pointer To Manager
		void AddPointer(void * Pointer,const c8 * FileName,s32 LineNum,s32 Type){}

		//! Remove Pointer From Manager
		void RemovePointer(void * Pointer,const c8 * FileName,s32 LineNum,s32 Type){}

		//! Grab memory manager like junknown work
		void MemoryGrab(){}

		//! Drop memory manager like junknown work
		void MemoryDrop(){}
	}
}


#endif
